//
//  main.m
//  UITestingKit
//
//  Created by Ofri Wolfus on 20/02/07.
//  Copyright 2007 Ofri Wolfus. All rights reserved.
//

#import <Cocoa/Cocoa.h>
#import <UITestingKit/UITestingKit.h>
#import "DPTestsRunner.h"
#include <stdlib.h>


static int printHelp(void) {
	printf("Usage: TestsRunner [options] [-a /path/to/executable] paths\n"
		   "Available options:\n"
		   "   --force            Ignore failed tests and try to execute all tests\n"
		   "   --nocatch          Don't catch uncaught exceptions. Use this together\n"
		   "                        with a breakpoint at -[NSException raise] in order\n"
		   "                        to debug tests.\n"
		   "   -v                 Verbose mode.\n"
		   "   -h,--help          Print this help message.\n"
		   "   -a                 UI application to launch before testing.\n"
		   "   -s				  Print statistics when done.\n"
		   "   --stackTrace       Print stack trace when UI tests fail.\n"
		   "\n\n"
		   "The '-a' flag is optional. \"Normal\" unit tests can still be performed as usual,\n"
		   "and more than one application can be tested at once.\n\n"
		   "Followed by the application's path are paths to tests modules.\n"
		   "A tests module MUST end with a .testsModule extension or it will be considered\n"
		   "as a directory.\n"
		   "Paths to directories will be searched for modules inside their top level,\n"
		   "and if found, will be loaded as well. Statistics are displayed after all\n"
		   "tests are completed.\n");
	
	return 0;
}

static NSTask *launchApp(NSString *path) {
	NSTask *task = nil;
	
	if ([DPUIElement accessibilityAPIEnabled]) {
		NSNumber *timeout = [[[NSProcessInfo processInfo] environment] objectForKey:@"DPLAUNCH_TIMEOUT"];
		
		// Launch our tested app
		task = [NSTask launchedTaskWithLaunchPath:path
										arguments:[NSArray array]];
		
		// Wait for our app to finish launching
		[[NSDistributedNotificationCenter defaultCenter] waitForNotification:@"DPApplicationFinishedLaunching"
																	  object:[path lastPathComponent]
																	 timeout:timeout ? [timeout doubleValue] : 0.0];
		
		// Wait for the accessibility API to completely set up
		sleep(1);
		
		// Set the global application element
		DPTestSubject = [[DPUIElement applicationElement:[task processIdentifier]] retain];
		
	} else {
		// When this function is called, it's known we need the tests subject,
		// so if accessibility is disabled we'll just error and terminate.
		printf("Error: Accessibility API is disabled!\n");
		exit(EXIT_FAILURE);
	}
	
	return task;
}

#define FLAG_FORCE			@"--force"
#define FLAG_NO_CATACH		@"--nocatch"

int main (int argc, const char * argv[]) {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	NSMutableArray *args = [[[NSProcessInfo processInfo] arguments] mutableCopy];
	BOOL stopOnFailure = ![args containsObject:FLAG_FORCE];
	BOOL catchExceptions = ![args containsObject:FLAG_NO_CATACH];
	BOOL verbose = [args containsObject:@"-v"];
	BOOL printLog = [args containsObject:@"-s"];
	BOOL printStackTrace = [args containsObject:@"--stackTrace"];
	DPTestsRunner *runner = [[DPTestsRunner alloc] init];
	NSTask *task = nil;
	int retCode = 0;
	unsigned index = 1U;
	NSRange pathsRange;
	NSArray *modules = nil;
	
	// Check for help flags
	if ([args containsObject:@"-h"] || [args containsObject:@"--help"])
		return printHelp();
	
	// Clean up our arguments array
	if (!stopOnFailure)
		[args removeObject:FLAG_FORCE];
	if (!catchExceptions)
		[args removeObject:FLAG_NO_CATACH];
	if (verbose)
		[args removeObject:@"-v"];
	if (printLog)
		[args removeObject:@"-s"];
	if (printStackTrace)
		[args removeObject:@"--stackTrace"];
	
	// Check for a "-a" flag. If we don't have it, it means we're
	// doing traditional unit testing (no other app involved).
	index = [args indexOfObject:@"-a"];
	if (index == NSNotFound)
		pathsRange = NSMakeRange(1U, [args count] - 1U);
	else
		pathsRange = NSMakeRange(index + 2U, [args count] - 3U /* index 0 + "-a" + app path */);
	
	// Set up our tests runner.
	[runner loadModules:[args subarrayWithRange:pathsRange]];
	modules = [[runner modules] retain];
	
	// If we got no paths and/or these paths are not tests modules,
	// our tests runner will have no modules.
	if ([modules count]) {
		NSString *log = nil;
		NSString *path = nil;
		
		// -v support
		[runner setVerbose:verbose];
		
		// Launch the app if we got one
		if (index != NSNotFound) {
			path = [args objectAtIndex:index + 1];
			
			if ([[path pathExtension] isEqualToString:@"app"])
				path = [[NSBundle bundleWithPath:path] executablePath];
		}
		
		if ([runner hasUnitTests]) {
			if (path)
				// Launch the test subject
				task = [launchApp(path) retain];
			
			// Run all tests.
			// This method sets up an autorelease pool and a runloop.
			// The runloop will process all tests async, and this method will return
			// only after all tests are completed (or a failure stopped the process).
			// Although this API (DPTestsRunner) is synchronized, the actual testing is not.
			retCode = [runner runIgnoringExceptions:catchExceptions
									  stopOnFailure:stopOnFailure
												log:printLog ? &log : NULL];
			
			// Print the log if needed
			if (printLog)
				printf("%s", [log UTF8String]);
			
			// These are going to change when we relaunch our app for UI testing
			[DPTestSubject release]; DPTestSubject = nil;
			[task terminate]; [task release];
		}
		
		// Do UI testing
		if (retCode == 0 && path && [runner hasUITests]) {	
			task = [launchApp(path) retain];
			retCode = [runner runUITests:printStackTrace];
		}
	} else {
		printHelp();
	}
	
	// Clean up
	[runner release];
	[DPTestSubject release]; DPTestSubject = nil;
	[task terminate]; [task release];
	[pool release];
	return retCode;
}
